/*
 * AudioData.java
 *
 * Copyright (c) 2010 Jaymie Strecker (strecker at cs.umd.edu). All rights reserved.
 *
 * This file is part of Tonic.
 *
 * Tonic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tonic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tonic.  If not, see <http://www.gnu.org/licenses/>.
 */

package tonic.audio;

import javax.sound.sampled.*;
import java.io.*;
import java.util.ArrayList;
import tonic.error.InternalException;

/**
 * AudioData represents an audio file and an accompanying list of excerpts.
 *
 * @author Jaymie Strecker (strecker at cs.umd.edu)
 * @author Ben Strecker
 */
public class AudioData {
    private File file;
    private float[] samples;
    private float samplesPerSecond;
    private ArrayList<Excerpt> excerpts;

    /**
     * Constructs an AudioData with the given file and an empty list of excerpts.
     *
     * @param file  a readable audio file
     * @throws IOException  if there is a problem reading file as an audio file
     */
    protected AudioData(File file) throws IOException {
        setFile(file); 
        excerpts = new ArrayList<Excerpt>();
    }

    /**
     * Sets the file. Does not affect the excerpt list.
     *
     * @param file  a readable audio file
     * @throws IOException  if there is a problem reading file as an audio file
     */
    protected void setFile(File file) throws IOException {
        AudioInputStream audioInputStream = null;
        try {
            audioInputStream = AudioSystem.getAudioInputStream(file);
            // Get info about the WAV file

            float frameRate = audioInputStream.getFormat().getFrameRate();
            float sampleRate = audioInputStream.getFormat().getSampleRate();
            if (frameRate != sampleRate) {
                throw new UnsupportedOperationException("Frame rate (" + frameRate +
                        ") differs from sample rate (" + sampleRate + ")");
            }

            samples = new AudioReader(audioInputStream, file).readAudio();
            this.file = file;
            samplesPerSecond = sampleRate;
        } catch (UnsupportedAudioFileException e) {
            throw new IOException(e);
        } catch (UnsupportedOperationException e) {
            throw new IOException(e);
        } finally {
            try {
                if (audioInputStream != null) {
                    audioInputStream.close();
                }
            } catch (IOException e) {
                // nop
            }
        }
    }

    /**
     * Sets the excerpt list. Does not affect the file. 
     *
     * @param excerpts  the excerpt list - assumed to be valid
     */
    protected void setExcerpts(ArrayList<Excerpt> excerpts) {
        this.excerpts = new ArrayList<Excerpt>(excerpts);
    }

    protected AudioData(float[] samples, float samplesPerSecond) {
        this.samples = new float[samples.length]; 
        System.arraycopy(samples, 0, this.samples, 0, samples.length);
        this.samplesPerSecond = samplesPerSecond;
        file = new File("(array of samples)");
    }

    protected void fillWithSamples(float[] toFill, int startIndex, int endIndex) {
        // make this arraycopy
        for (int i = startIndex; i < endIndex; i++) {
            toFill[i - startIndex] = samples[i];
        }
    }

    protected float getSample(int index) { return samples[index]; }

    protected File getFile() { return file; }

    protected float getSamplesPerSecond() { return samplesPerSecond; }

    protected int getNumSamples() { return samples.length; }

    @Override
    public String toString() {
        return file.getPath();
    }

    /**
     * Adds the excerpt specified by the given end-points.
     *
     * If the new excerpt would overlap an existing excerpt, the new excerpt is broken
     * into pieces (or skipped) to fit around the existing excerpt, which is not
     * modified.
     *
     * Excerpts are added in ascending order of sample number.
     */
    public void addExcerpt(int startSample, int endSample) {
        if (excerpts.size() == 0) {
            excerpts.add(new Excerpt(this, startSample, endSample));
        } else {
            for (int i = 0; i < excerpts.size(); i++) {
                Excerpt currExcerpt = excerpts.get(i);
                int currStart = currExcerpt.getStartSampleNum();
                int currEnd = currExcerpt.getEndSampleNum();
                if (startSample < currStart && currStart <= endSample && endSample <= currEnd) {
                    // Existing excerpt overlaps endSample
                    // E:      |------|
                    // N: |------|
                    //    |----|------|
                    excerpts.add(i, new Excerpt(this, startSample, currStart));
                    break;
                }
                if (currStart <= startSample && startSample <= currEnd && currEnd < endSample) {
                    // Existing excerpt overlaps startSample
                    // E: |------|
                    // N:      |------|
                    //    |------|----|
                    startSample = currEnd;
                }
                if (startSample < currStart && currEnd < endSample) {
                    // Existing excerpt contained within new excerpt
                    // E:    |-----|
                    // N: |-----------|
                    //    |--|-----|--|
                    excerpts.add(i, new Excerpt(this, startSample, currStart));
                    startSample = currEnd;
                }
                if (currStart <= startSample && endSample <= currEnd) {
                    // New excerpt contained within existing excerpt
                    // E: |-----------|
                    // N:    |-----|
                    //    |-----------|
                    break;
                }
                if (endSample < currStart) {
                    // No overlap, and the new excerpt should go here
                    excerpts.add(i, new Excerpt(this, startSample, endSample));
                    break;
                }
                if (i == excerpts.size() - 1) {
                    // No overlap, and the new excerpt should go after the last excerpt
                    excerpts.add(i + 1, new Excerpt(this, startSample, endSample));
                    break;
                }
            }
        }
    }

    /**
     * Returns the excerpt at the given index.
     *
     * @param index     index into the list of excerpts
     */
    public Excerpt getExcerpt(int index) {
        return excerpts.get(index);
    }

    /**
     * Returns the number of excerpts created so far.
     */
    public int getNumExcerpts() {
        return excerpts.size();
    }

    /**
     * Analyzes (calculates pitches for) any excerpts that have not yet been analyzed.
     *
     * @throws InternalException    if there is a problem analyzing the pitches
     */
    public void analyzeExcerpts() throws InternalException {
        for (int i = 0; i < excerpts.size(); i++) {
            Excerpt e = excerpts.get(i);
            if (e.getRankedPitches() == null) {
                e.analyzeSpectrum();
                e.analyzePitches();
            }
        }
    }


}
